Vue - Object.defineProperty & Proxy


Posted by TempuraEngineer on 2023-04-15

目錄


Vue2

Object.defineProperty()

defineProperty()是Object的靜態方法,透過其可以在物件上新增屬性,或修改物件的屬性

它接收3個參數分別為obj、property、descriptor

其中descriptor會是物件,分為data descriptors(帶有value、writable屬性的物件)、accessor descriptors(帶有getter和setter的物件)

注意2種不能混用,不然會得到"Invalid property descriptor. Cannot both specify accessors and a value or writable attribute"

If a descriptor has neither of value, writable, get and set keys, it is treated as a data descriptor. If a descriptor has both [value or writable] and [get or set] keys, an exception is thrown.

const foo = {};

Object.defineProperties(foo, {
    a:{ // writable預設為false
        value:33
    },
    b:{ // data descriptors,所以不能有getter和setter
        value:32,
        writable:true        
    },

})

console.log(foo.a, foo.b); // 33, 32

foo.a = 102;
foo.b = 101;

console.log(foo.a, foo.b); // 33, 101


響應式系統

響應式系統是透過Object.defineProperty()實現的,之所以它是因為要支援一些老瀏覽器(ex:IE8)

export default Vue.extend({
    name: 'Test',
    data(){
      return{
        count:33,
      }
    },
    beforeCreate(){
        console.log(this.count); // undefined
    },
    created(){
        console.log(this.count); // 33
    }
})

Vue - Lifecycle Hooks

beforeCreate階段,Vue實例(instance)已被初始化,但options還不能使用

Called immediately when the instance is initialized, after props resolution, before processing other options such as data() or computed

created階段options會被設置好,所以可以透過this讀取count的值了。但$el還沒未被設為#app或組件的template

在beforeCreate和created間會初始化響應系統(init reactivity),這時會做類似以下的動作

class Foo{
    constructor(options){
        this.initReactivity(options.data());
    }

    // 初始化響應系統
    initReactivity(data){
        const keys = Object.getOwnPropertyNames(data);    
        keys.forEach(key => {
            Object.defineProperty(this, key, {
                configurable:true,
                get(){
                    console.log('get');
                    return data[key];
                },
                set(val){
                    console.log('set');

                    if(val === data[key]) return;

                    data[key] = val;
                }
            })
        })    
    }
}

// 實例已被建立
const foo = new Foo({
  data(){
    return {
      count:33
    }
  }
});

console.log(foo.count); // 33

foo.count = 25; // 先get,後set
console.log(foo.count); // 25

無法監聽陣列元素、物件屬性的增刪、新增的屬性的變化。Map、Set、Class等,也不行

const foo = new Foo({
  data(){
    return {
      person:{
        name:'Emma',
      },
      arr:[1]
    }
  }
});

foo.arr[0] = 222; // get

foo.person.age = 12; // get

foo.num = 50; // 都沒有觸發

新手常見的畫面沒有隨資料改變重新渲染也是這個緣故


Vue3

Proxy

透過Proxy可以建立一個物件來替代原本的物件,Proxy常用於屬性的存取、驗證

接收2個參數分別為target、handler(又稱作trap),前者為原本的物件,後者為一個用於定義何時攔截、攔截時會做甚麼的物件

const person = {
  name:'alex',
}

const px = new Proxy(person, {
  get(obj, prop, val){
    console.log(`get ${prop}`);
    return obj[prop];
  },
  set(obj, prop, val){ // obj為原物件, prop為屬性名
    console.log(`set ${prop}`);
    obj[prop] = val;
  }
});

console.log(px.name); // get name

px.name = 'emma'; // set name
px.age = 25; // set name


Reflect

Reflect與Proxy用途相當類似,但它能接收第三個參數(receiver)

recevier可以視為是指定函式其中的this的指向

const obj = {
  get foo(){
    return this.foo;
  }
}

// 將this的指向改為第三個參數
console.log(Reflect.get(obj, 'foo', {foo:33})); // 33

不過Reflect並不能改變原本就有指向的this的指向

const obj = {
    foo:1,
    get bar(){
        return this.foo;
    }
}

console.log(Reflect.get(obj, 'foo', {foo:33})); // 1

Proxy加上Reflect可以完成一個簡單的響應式系統

再詳細我也不清楚了QQ


參考資料

MDN Object.defineProperty()
MDN - Proxy
一文帶你深入剖析vue3的響應式
聽說你很瞭解 Vue3 響應式?


#Vue #proxy #Object.defineProperty







Related Posts

What Type of Laser Engraving Machine Should be Used for Stainless Steel Engraving?

What Type of Laser Engraving Machine Should be Used for Stainless Steel Engraving?

盒模型 微筆記

盒模型 微筆記

Jest "Cannot find module from xxx" issue

Jest "Cannot find module from xxx" issue


Comments